# -*- coding: utf-8 -*-
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# Copyright 2009, David L. Miller
#
import wx,serial,os,struct,binascii
from copy import copy

# Choices

baudrates = ['50','75','110','134','150','200','300','600','1200',
      '1800','2400','4800','9600','19200','38400','57600',
      '115200','230400','460800','500000','576000','921600',
      '1000000','1152000','1500000','2000000','2500000',
      '3000000','3500000','4000000']

parity = { 'odd':serial.PARITY_ODD,'even':serial.PARITY_EVEN,'none':serial.PARITY_NONE,'names':serial.PARITY_NAMES }
stopbits = { '1':serial.STOPBITS_ONE,  '2':serial.STOPBITS_TWO }
bytesize = { '5':serial.FIVEBITS, '6':serial.SIXBITS, '7':serial.SEVENBITS, '8':serial.EIGHTBITS }

sdev=[]
basedev = '/dev/'
devs = os.listdir(basedev)
for dev in devs:
	if 'ttyS' in dev: sdev.append(dev)
	elif 'ttyUSB' in dev: sdev.append(dev)
	elif 'ttyPS' in dev: sdev.append(dev)
sdev.sort()

ID_BIN = 1
ID_NL = 2
ID_QUIT = 3

class Terminal(wx.Frame):
	ser = None
	MAXLEN = 1000
	recall = []
	recalltmp = []
	recordthis = 0
	def __init__(self, *args, **kwds):
		kwds["style"] = wx.DEFAULT_FRAME_STYLE
		wx.Frame.__init__(self, None, -1, "Terminal Troll",size=(800,800), pos=(0, 0), style=wx.DEFAULT_FRAME_STYLE)
		self.timer = wx.Timer(self, id=102)
		self._layout()
		self._binds()
		self.SetSize((825,445))
		menubar = wx.MenuBar()
		file = wx.Menu()
		#view = wx.Menu()
		self.viewbin = file.Append(ID_BIN, 'Display Hex', 'Display Hex', kind=wx.ITEM_CHECK)
		self.newline = file.Append(ID_NL, 'Append Newline (\\r\\n)', kind=wx.ITEM_CHECK)
		file.AppendSeparator()
		file.Append(ID_QUIT, '&Quit', 'Exits the Program')

		self.Bind(wx.EVT_MENU, self.DisplayBin, id=ID_BIN)
		self.Bind(wx.EVT_MENU, self.OnQuit, id=ID_QUIT)

		menubar.Append(file, '&File')
		self.SetMenuBar(menubar)


		
	def _layout(self):
		sizer1 = wx.BoxSizer(wx.HORIZONTAL)

		# Create Send/Receive Boxes
		sendbox = wx.StaticBox(self, -1, 'Send')
		sizer2a = wx.StaticBoxSizer(sendbox,wx.VERTICAL)
		sendsizer = wx.FlexGridSizer(2, 1, 0, 0)
		self.sendtxtview = wx.TextCtrl(self, -1,"",size=(300, 341), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.NO_BORDER)
		self.sendtxt = wx.TextCtrl(self, -1,"",size=(300, 24),style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER|wx.NO_BORDER)
		self.sendtxt.SetFocus()
		self.sendtxt.SetValue('Enter Text Here')
		self.sendtxt.SetInsertionPoint(0)
		self.sendtxtview.SetForegroundColour('Blue')
		self.sendtxt.SetBackgroundColour('#DDFFDD')
		sendsizer.Add(self.sendtxtview,1,wx.TOP|wx.LEFT|wx.RIGHT,5)
		sendsizer.Add(self.sendtxt,1,wx.LEFT|wx.RIGHT,5)
		sizer2a.Add(sendsizer)
		sizer1.Add(sizer2a, 1, wx.ALL, 5)

		rcvbox = wx.StaticBox(self, -1, 'Receive')
		sizer2b = wx.StaticBoxSizer(rcvbox,wx.VERTICAL)
		self.rcvtxt = wx.TextCtrl(self, -1,"",size=(300, 365), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.NO_BORDER)
		sizer2b.Add(self.rcvtxt,1,wx.ALL,5)
		self.rcvtxt.SetForegroundColour('Red')
		self.rcvtxt.SetMaxLength(100)
		sizer1.Add(sizer2b, 1, wx.ALL, 5)
		
		# Create Serial Options
		#optbox = wx.StaticBox(self, -1, 'Options')
		#sizer2c = wx.StaticBoxSizer(optbox,wx.VERTICAL)
		widmin = (120,27)
		hfwidmin = (58,27)
		optsizer = wx.FlexGridSizer(5,1,0,0)

		devbox = wx.StaticBox(self, -1, 'Port')
		sizerdev = wx.StaticBoxSizer(devbox,wx.VERTICAL)
		self.dev = wx.Choice(self,-1,choices=sdev)
		self.dev.SetMinSize(widmin)
		sizerdev.Add(self.dev, 1, wx.ALL|wx.ADJUST_MINSIZE, 2)

		baudbox = wx.StaticBox(self, -1, 'Baudrate')
		sizerbaud = wx.StaticBoxSizer(baudbox,wx.VERTICAL)
		self.baudrate = wx.Choice(self,-1,choices=baudrates)
		self.baudrate.SetMinSize(widmin)
		sizerbaud.Add(self.baudrate, 1, wx.ALL|wx.ADJUST_MINSIZE, 2)
		
		parbox = wx.StaticBox(self, -1, 'Parity')
		sizerpar = wx.StaticBoxSizer(parbox, wx.VERTICAL)
		self.parity = wx.Choice(self,-1,choices=parity.keys())
		self.parity.SetMinSize(widmin)
		sizerpar.Add(self.parity,1,wx.ALL|wx.ADJUST_MINSIZE,2)
		
		stopbox = wx.StaticBox(self, -1, 'Stopbits')
		sizerstop = wx.StaticBoxSizer(stopbox, wx.VERTICAL)
		self.stopbits = wx.Choice(self,-1,choices=stopbits.keys())
		self.stopbits.SetMinSize(widmin)
		sizerstop.Add(self.stopbits,1,wx.ALL|wx.ADJUST_MINSIZE,2)
		
		bytebox = wx.StaticBox(self, -1, 'Bytesize')
		sizerbyte = wx.StaticBoxSizer(bytebox, wx.VERTICAL)
		self.bytesize = wx.Choice(self,-1,choices=bytesize.keys())
		self.bytesize.SetMinSize(widmin)
		sizerbyte.Add(self.bytesize,1,wx.ALL|wx.ADJUST_MINSIZE,2)
		
		recbox = wx.StaticBox(self, -1, 'Record')
		sizerrec = wx.StaticBoxSizer(recbox, wx.VERTICAL)
		sizerrecbox = wx.BoxSizer(wx.HORIZONTAL)
		self.rec = wx.Button(self,-1,'Rec')
		self.rec.SetMinSize(hfwidmin)
		self.recstop = wx.Button(self,-1,'Stop')
		self.recstop.SetMinSize(hfwidmin)
		sizerrecbox.Add(self.rec,1,wx.ALL|wx.ADJUST_MINSIZE,2)
		sizerrecbox.Add(self.recstop,1,wx.ALL|wx.ADJUST_MINSIZE,2)
		sizerrec.Add(sizerrecbox,1,wx.ALL|wx.ADJUST_MINSIZE,2)

		openbox = wx.StaticBox(self, -1, 'Open/Close')
		sizeropen = wx.StaticBoxSizer(openbox, wx.VERTICAL)
		self.openclose = wx.ToggleButton(self,-1,'Open')
		self.openclose.SetMinSize(widmin)
		sizeropen.Add(self.openclose,1,wx.ALL|wx.ADJUST_MINSIZE,2)

		optsizer.Add(sizerdev,1,wx.ALL,2)
		optsizer.Add(sizerbaud,1,wx.ALL,2)
		optsizer.Add(sizerpar,1,wx.ALL,2)
		optsizer.Add(sizerstop,1,wx.ALL,2)
		optsizer.Add(sizerbyte,1,wx.ALL,2)
		optsizer.Add(sizerrec,1,wx.ALL,2)
		optsizer.Add(sizeropen,1,wx.ALL,2)
		#sizer2c.Add(optsizer)
		
		sizer = wx.FlexGridSizer(1, 2, 0, 0)
		sizer.Add(sizer1, 1, wx.ALL, 5)
		sizer.Add(optsizer, 1, wx.ALL, 5)
		
		
		self.SetSizer(sizer)
		self.Fit()

	def _binds(self):
		self.openclose.Bind(wx.EVT_TOGGLEBUTTON,self.OnOC)
		self.Bind(wx.EVT_TIMER, self.GetBuffer, id=102)
		self.sendtxt.Bind(wx.EVT_KEY_DOWN, self.KeyPressed)
		self.sendtxt.Bind(wx.EVT_TEXT_ENTER,self.OnEnter)
		self.rec.Bind(wx.EVT_BUTTON,self.Record)
		self.recstop.Bind(wx.EVT_BUTTON,self.StopRecord)

	def Record(self,event):
		self.fh = open('recording.txt','wb')
		self.recordthis = 1

	def StopRecord(self,event):
		if hasattr(self,'fh'): self.fh.close()
		self.recordthis = 0
	
	def DisplayBin(self, event):
		if self.viewbin.IsChecked():
			text=self.rcvtxt.GetValue()
			text=binascii.hexlify(text)
			self.rcvtxt.SetValue(text)
			self.rcvtxt.SetInsertionPointEnd()
		else:
			text=self.rcvtxt.GetValue()
			text=binascii.unhexlify(text)
			self.rcvtxt.SetValue(text)
			self.rcvtxt.SetInsertionPointEnd()

	def KeyPressed(self,event):
		keycode = event.GetKeyCode()
		if keycode == wx.WXK_UP:
			self.recalltmp.insert(0,self.recalltmp.pop())
			self.sendtxt.SetValue(self.recalltmp[0])
			self.sendtxt.SetInsertionPointEnd()
		elif keycode == wx.WXK_DOWN:
			pos = len(self.recalltmp)
			self.recalltmp.insert(pos,self.recalltmp.pop(0))
			self.sendtxt.SetValue(self.recalltmp[0])
			self.sendtxt.SetInsertionPointEnd()
		elif keycode == wx.WXK_BACK:
			self.recalltmp = copy(self.recall)
		event.Skip()

	def OnQuit(self,event):
		self.CloseSerial()
		self.Close()

	def OnEnter(self,event):
		text=self.sendtxt.GetValue()
		self.recall.append(text)
		self.recalltmp = copy(self.recall)
		if self.ser.isOpen():
			self.sendtxt.SetValue('')
			self.sendtxtview.AppendText('> '+text+'\n')
			if self.newline.IsChecked(): self.ser.write(str(text) + '\r\n')
			else: self.ser.write(str(text))

	def OnOC(self,event):
		if self.openclose.GetValue():
			self.dev.Disable()
			self.baudrate.Disable()
			self.parity.Disable()
			self.bytesize.Disable()
			self.stopbits.Disable()
			self.sendtxt.Enable()
			self.openclose.SetLabel('Close')
			self.OpenSerial()
			self.timer.Start(2)
		else:
			self.dev.Enable()
			self.baudrate.Enable()
			self.parity.Enable()
			self.bytesize.Enable()
			self.stopbits.Enable()
			self.sendtxt.Disable()
			self.openclose.SetLabel('Open')
			self.timer.Stop()
			self.CloseSerial()

	def GetBuffer(self,event):
		if hasattr(self.ser,'isOpen') and self.ser.isOpen():
			recv = ''
			bin = ''
			while self.ser.inWaiting() > 0:
				if self.viewbin.IsChecked():
					raw = self.ser.read(self.ser.inWaiting())
					bin += raw
					recv += binascii.hexlify(raw)
				else:
					raw = self.ser.read(self.ser.inWaiting())
					bin += raw
					recv += raw
			if not recv=='':
				self.rcvtxt.AppendText(recv)
				if len(self.rcvtxt.GetValue()) > self.MAXLEN:
					self.rcvtxt.SetValue(self.rcvtxt.GetValue()[-self.MAXLEN:])
					self.rcvtxt.SetInsertionPointEnd()
				if self.recordthis and hasattr(self,'fh'): self.fh.write(bin)

	def OpenSerial(self):
		self.ser = serial.Serial(
			port=basedev+self.dev.GetStringSelection(),
			baudrate=int(self.baudrate.GetStringSelection()),
			parity=parity[self.parity.GetStringSelection()],
			stopbits=stopbits[self.stopbits.GetStringSelection()],
			bytesize=bytesize[self.bytesize.GetStringSelection()])
		self.ser.timeout = 0.5
		self.ser.open() # open the port
		if not self.ser.isOpen(): self.sendtxtview.AppendText('** Could Not Open Device **\n')

	def CloseSerial(self):
		if hasattr(self.ser,'isOpen'):
			if self.ser.isOpen(): self.ser.close()

class MyApp(wx.App):
	def OnInit(self):
		frame = Terminal()
		self.SetTopWindow(frame)
		frame.Show()
		return 1


app = MyApp(0)
app.MainLoop()
